import json
import random
import time
import traceback
import codecs
import redis
from contextlib import contextmanager

import cv2
import base64
import re
# from selenium import webdriver
from seleniumwire import webdriver  # Import from seleniumwire
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

# 导入键盘
from selenium.webdriver.common.keys import Keys
# from PositionalPID import get_pid_track

@contextmanager
def selenium(driver):
    try:
        yield
    except Exception as e:
        traceback.print_exc()
        driver.quit()


class SliderCaptcha():

    def __init__(self):
        # 创建一个参数对象，用来控制chrome以无界面模式打开
        chrome_options = webdriver.ChromeOptions()
        # chrome_options.add_argument('--headless')  # # 浏览器不提供可视化页面
        chrome_options.add_argument('--disable-gpu')  # 禁用GPU加速,GPU加速可能会导致Chrome出现黑屏，且CPU占用率高达80%以上
        chrome_options.add_argument('--no-sandbox')
        # chrome_options.add_argument('--disable-gpu')
        chrome_options.add_argument('--disable-dev-shm-usage')
        chrome_options.add_experimental_option('excludeSwitches', ['enable-automation'])
        self.driver = webdriver.Chrome(options=chrome_options)

        self.wait = WebDriverWait(self.driver, 5)

        self.count = 5

        self.redis = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

    def login(self):
        with selenium(self.driver):
            login_url = 'https://jujuso.com/#/login'
            self.driver.maximize_window()
            self.driver.get(url=login_url)

            # 输入账号
            username = self.wait.until(
                EC.element_to_be_clickable((By.XPATH, '//input[@id="username"]'))
            )
            username.send_keys('15267016823')
            print('账号已输入')
            # time.sleep(1)
            # 输入密码
            password = self.wait.until(
                EC.element_to_be_clickable((By.XPATH, '//input[@id="password"]'))
            )
            password.send_keys('333123')
            print('密码已输入')

            # 登录框
            submit = self.wait.until(
                EC.element_to_be_clickable(
                    (By.XPATH, '//button[@class="semi-button semi-button-primary semi-button-block"]'))
            )
            submit.click()
            print('点击登录按钮')
            time.sleep(1)

            return self.check_login_slider(login_url)

    def search(self):

        with selenium(self.driver):

            search_url = 'https://jujuso.com'
            self.driver.get(url=search_url)

            self.set_local_storage()
            self.driver.get(url=search_url)

            # 获取滑动前页面的url网址
            start_url = self.driver.current_url

            # 找到输入框. 输入要搜索的内容  python  =>  输入回车/点击搜索按钮
            keyword = self.wait.until(
                EC.element_to_be_clickable((By.XPATH, '//input'))
            )

            keyword.send_keys('字节跳动')

            keyword.send_keys(Keys.ENTER)
            print('关键词已输入')
            time.sleep(2)

            end_url = self.driver.current_url

            if start_url != end_url:

                # 点击空白处
                root = self.wait.until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//div[@id="root"]'))
                )
                root.click()

                # 先点此验证
                submit = self.wait.until(
                    EC.element_to_be_clickable(
                        (By.XPATH, '//div[@class="flex justify-center my-2"]/button'))
                )

                submit.click()
                print('点击按钮开始验证')
                time.sleep(1)

                self.check_search_slider()

    def set_local_storage(self):
        key = "persist:ujuso"
        # 设置localstorage
        cookie = self.redis.get('ujuso_cookie')
        print("cookie", cookie)
        self.driver.execute_script("window.localStorage.setItem(arguments[0], arguments[1]);", key, cookie)

    def update_kid(self):

        for request in self.driver.requests:
            res = re.match('^https://api.juyunso.com/v1/api_search/free?.+', request.url)

            if res is not None and request.response.status_code == 200:
                body = request.response.body
                print('body', body)
                code = json.loads(body).get('code')
                if code == 200:
                    kid = request.params.get('kid')
                    print('kid', kid)

                    self.redis.set('ujuso_kid', kid)
                    return kid
                elif code == 403: #登录信息过期
                    # 登录信息过期
                    self.redis.delete('ujuso_token')
                    self.login()
                    return None

                continue

        return None

    def onload_save_img(self, url, filename="image.png"):
        '''
        下载图片并保存
        :param url: 图片网址
        :param filename: 图片名称
        :return:
        '''

        try:
            image_data = re.sub('^data:image/.+;base64,', '', url)
            image_data = base64.b64decode(image_data)
            with open(filename, 'wb') as f:
                f.write(image_data)
        except Exception as e:
            print('图片下载失败')
            raise e
        else:
            print('图片下载成功')

    def move_slider(self, distance):
        # 初始滑块距离边缘 4 px
        trajectory = self.get_tracks(distance)
        print(trajectory)

        # 等待按钮可以点击
        slider_element = self.wait.until(
            EC.element_to_be_clickable(
                (By.CLASS_NAME, 'slide-btn'))
        )
        # 添加行动链
        ActionChains(self.driver).click_and_hold(slider_element).perform()
        for track in trajectory['plus']:
            ActionChains(self.driver).move_by_offset(
                xoffset=track,
                yoffset=round(random.uniform(4.0, 5.0), 1)
            ).perform()
        time.sleep(0.5)

        for back_tracks in trajectory['reduce']:
            ActionChains(self.driver).move_by_offset(
                xoffset=back_tracks,
                yoffset=round(random.uniform(2.0, 3.0), 1)
            ).perform()
        #
        for i in [-4, 4]:
            ActionChains(self.driver).move_by_offset(
                xoffset=i,
                yoffset=0
            ).perform()

        time.sleep(0.1)
        ActionChains(self.driver).release().perform()

    def check_login_slider(self, start_url):
        num = 1
        # while True:
        while num <= self.count:
            distance = self.findfic(target='./background.png', template='./slider.png')
            print('滑动距离是', distance)

            self.move_slider(distance)
            # self.start_move(distance)
            # self.slide_verify(distance)
            time.sleep(2)

            end_url = self.driver.current_url
            print('end_url:', end_url)

            if end_url == start_url:
                print('第%s次验证失败...' % num, '\n')
                num = num + 1
                self.refresh()
                continue

            print('登录验证通过...', '\n')

            return self.update_token()
        # 登录失败
        return None

    def check_search_slider(self):
        num = 1
        # while True:
        while num <= self.count:
            distance = self.findfic(target='./background.png', template='./slider.png')
            print('滑动距离是', distance)

            self.move_slider(distance)
            time.sleep(2)

            res = self.update_kid()
            print('校验结果', res)

            if res == 200:
                break
            elif res is None:
                print('第%s次验证失败...' % num, '\n')
                num = num + 1
                self.refresh()
            else:
                break

    def refresh(self):
        submitBtn = self.wait.until(
            EC.element_to_be_clickable(
                (By.XPATH, '//button[@class="refresh-btn btn"]'))
        )

        submitBtn.click()
        print('刷新滑块验证码')
        time.sleep(1)
    def get_cookies(self):
        '''
        登录成功后 保存账号的cookies
        :return:
        '''
        cookies = self.driver.get_cookies()
        self.cookies = ''
        for cookie in cookies:
            self.cookies += '{}={};'.format(cookie.get('name'), cookie.get('value'))

    def update_token(self):
        # Access requests via the `requests` attribute
        for request in self.driver.requests:
            print('request_url', request.url)
            if request.url == 'https://api.juyunso.com/v1/user/login' and request.response.body is not None:

            # if request.url == 'https://api.juyunso.com/v1/user/login':
                print('request', request)
                print('response', request.response)
                body = request.response.body
                token = json.loads(body).get('data').get('token')
                print('token', token)

                # 更新
                # 通过键名获取localStorage值
                cookie = self.driver.execute_script('return window.localStorage.getItem("persist:ujuso")')
                # 更新缓存

                self.redis.set('ujuso_cookie', cookie)

                self.redis.set('ujuso_token', token)

                return token

        return None

    def close(self):
        self.driver.quit()

    def findfic(self, target='./background.png', template='./slider.png'):
        """
        :param target: 滑块背景图
        :param template: 滑块图片路径
        :return: 模板匹配距离
        """

        # 获取背景图并保存
        background = self.wait.until(
            # lambda x: x.find_element_by_xpath('//img[@class="bg"]')
            EC.presence_of_element_located((By.XPATH, '//img[@class="bg"]'))
        ).get_attribute('src')

        self.onload_save_img(background, target)

        # 获取滑块图并保存
        slider = self.wait.until(
            # lambda x: x.find_element_by_xpath('//img[@class="mask"]')
            EC.presence_of_element_located((By.XPATH, '//img[@class="mask"]'))
        ).get_attribute('src')

        self.onload_save_img(slider, template)

        target_rgb = cv2.imread(target)
        target_gray = cv2.cvtColor(target_rgb, cv2.COLOR_BGR2GRAY)
        template_rgb = cv2.imread(template, 0)
        # 使用相关性系数匹配， 结果越接近1 表示越匹配
        # https://www.cnblogs.com/ssyfj/p/9271883.html
        res = cv2.matchTemplate(target_gray, template_rgb, cv2.TM_CCOEFF_NORMED)
        # opencv 的函数 minMaxLoc：在给定的矩阵中寻找最大和最小值，并给出它们的位置
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        # 背景图缺口坐标
        print('当前滑块缺口位置', (min_val, min_loc, max_val, max_loc))
        # 因为滑块只需要 x 坐标的距离，放回坐标元组的 [0] 即可
        if abs(1 - min_val) <= abs(1 - max_val):
            distance = min_loc[0]
        else:
            distance = max_loc[0]
        return distance

    def get_tracks(self, distance):
        """

        :param distance: 缺口距离
        :return: 轨迹
        """
        # 分割加减速路径的阀值
        value = round(random.uniform(4.5, 5.5), 1)
        # 划过缺口 6 px
        distance += 1
        # 初始速度，初始计算周期， 累计滑动总距
        v, t, sum = 0, 0.3, 0
        # 轨迹记录
        plus = []
        # 将滑动记录分段，一段加速度，一段减速度
        mid = distance * value
        while sum < distance:
            if sum < mid:
                # 指定范围随机产生一个加速度
                a = round(random.uniform(10.0, 13.0), 1)
            else:
                # 指定范围随机产生一个减速的加速度
                a = -round(random.uniform(13.0, 16.0), 1)
            s = v * t + 0.5 * a * (t ** 2)
            v = v + a * t
            sum += s
            plus.append(round(s))

        # end_s = sum - distance
        # plus.append(round(-end_s))

        # 手动制造回滑的轨迹累积20px
        # reduce = [-3, -3, -2, -2, -2, -2, -2, -1, -1, -1]
        # reduce = [-6, -4, -6, -4]
        reduce = [-1]
        return {'plus': plus, 'reduce': reduce}

    def start_move(self, distance):
        # 等待按钮可以点击
        element = self.wait.until(
            EC.element_to_be_clickable(
                (By.CLASS_NAME, 'slide-btn'))
        )

        # 这里就是根据移动进行调试，计算出来的位置不是百分百正确的，加上一点偏移
        distance -= element.size.get('width') / 2
        distance += 25

        # 按下鼠标左键
        ActionChains(self.driver).click_and_hold(element).perform()
        time.sleep(0.5)
        while distance > 0:
            if distance > 10:
                # 如果距离大于10，就让他移动快一点
                span = random.randint(5, 8)
            else:
                # 快到缺口了，就移动慢一点
                span = random.randint(2, 3)
            ActionChains(self.driver).move_by_offset(span, 0).perform()
            distance -= span
            time.sleep(random.randint(10, 50) / 100)

        ActionChains(self.driver).move_by_offset(distance, 1).perform()
        ActionChains(self.driver).release(on_element=element).perform()

def t():
    spider = SliderCaptcha()
    res = spider.login()
    spider.close()
    return res
def k():
    spider = SliderCaptcha()
    res = spider.search()
    spider.close()
    return res

def tk():
    spider = SliderCaptcha()
    token = spider.login()

    kid = None
    if token is not None:
        kid = spider.search()

    spider.close()

    return {"token": token, "kdi": kid}

def run():
    # pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
    # r = redis.Redis(connection_pool=pool)
    r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
    while True:
        job = r.brpop('slider')
        if job == 't':
            t()
        elif job == 'k':
            k()
        elif job == 'tk':
            tk()
        else:
            print(job)
            val = r.get('str')
            print(val)



if __name__ == '__main__':
    # run()
    # tk()
    # t()
    k()